#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QTimer>
//#include <QDateTime>
#include <QDebug>


// quint16的高、低8位互换
inline static quint16 swap16(quint16 n)
{
    return n>>8 | n<<8;
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()),this,SLOT(time_update()));

    myCom = new QSerialPort(this);
    connect(myCom, &QSerialPort::readyRead, this, &MainWindow::readMyCom);

    //向绘图区域QCustomPlot(从widget提升来的)添加一条曲线
    for(int i=0;i<MSA9020_COUNT;i++) {
        mGraphA[i] = ui->mPlot->addGraph(ui->mPlot->xAxis, ui->mPlot->axisRect()->axis(QCPAxis::atRight, 0));
        mGraphB[i] = ui->mPlot->addGraph(ui->mPlot->xAxis, ui->mPlot->axisRect()->axis(QCPAxis::atRight, 0));
        QColor qc=QColor::fromHsl(rand()%360,rand()%256,rand()%200);
        mGraphA[i]->setPen(QPen(QColor(qc.red(), qc.green(), qc.blue())));
        qc=QColor::fromHsl(rand()%360,rand()%256,rand()%200);
        mGraphB[i]->setPen(QPen(QColor(qc.red(), qc.green(), qc.blue())));
    }
    //设置坐标轴标签名称
    ui->mPlot->xAxis->setLabel("时间");
    ui->mPlot->yAxis->setLabel("数值");
    ui->mPlot->yAxis->setRange(-100, 100);
    QDateTime dateTime = QDateTime::currentDateTime();
    double  now = dateTime.toTime_t();//当前时间转化为秒
    //生成时间刻度对象
    QSharedPointer<QCPAxisTickerDateTime> dateTimeTicker(new QCPAxisTickerDateTime);
    ui->mPlot->xAxis->setTicker(dateTimeTicker);
    //dateTimeTicker->setDateTimeSpec(Qt::UTC);//设施世界时间，即不加上时区的时间
    dateTimeTicker->setTickCount(24);
    dateTimeTicker->setTickStepStrategy(QCPAxisTicker::tssMeetTickCount);
    ui->mPlot->xAxis->setSubTicks(false);
    ui->mPlot->xAxis->setRange(now-3600*1, now);//, Qt::AlignRight);//x轴范围，从当前时间起往后推24小时
    dateTimeTicker->setDateTimeFormat("MM-dd h:m");//设置x轴刻度显示格式
    ui->mPlot->xAxis->setTickLabelRotation(30);//设置刻度标签顺时针旋转30度

    initfrom();
}

MainWindow::~MainWindow()
{

    if(myCom != NULL){
            if(myCom->isOpen()){
                myCom->close();
            }
            delete myCom;
        }
    delete ui;
}

void MainWindow::initfrom()
{
    QSettings configIni(QCoreApplication::applicationDirPath()+"/config.ini", QSettings::IniFormat);

    // 初始化界面
    for(int i=1;i<=MSA9020_COUNT;i++) {
        modCount[i-1] = 0;  // 数组初始化
        // modbus地址
        modaddr[i-1] = configIni.value("msa/addr"+QString::number(i)).toInt();
        this->findChild<QLabel*>("modbusAddr"+QString::number(i))->setText("MSA9020("+QString::number(modaddr[i-1])+")");
        // A\B通道区域名称
        this->findChild<QLabel*>("addrA"+QString::number(i))->setText(configIni.value("msa/a"+QString::number(i)).toString());
        this->findChild<QLabel*>("addrB"+QString::number(i))->setText(configIni.value("msa/b"+QString::number(i)).toString());
    }

    ComStatic = new QLabel("通讯端口状态", this);
    ComStatic->setMinimumWidth(200);
    ComStatic->setAlignment(Qt::AlignCenter);  //水平居中(HCenter)。
    statusBar()->addPermanentWidget(ComStatic); //现实永久信息

    QLabel *Copyright = new QLabel("<font>Copyright&#169; 2019</font>", this);
    Copyright->setMinimumWidth(200);
    Copyright->setAlignment(Qt::AlignCenter);  //水平居中(HCenter)。
    statusBar()->addPermanentWidget(Copyright); //现实永久信息

    // 串口参数设置
    openCom();
}

bool MainWindow::openCom()
{
    QSettings configIni(QCoreApplication::applicationDirPath()+"/config.ini", QSettings::IniFormat);
    // 串口参数设置
    QString portName = configIni.value("com/portname").toString();

    if(myCom->isOpen()) myCom->close();
    myCom->setPortName(portName);
    myCom->setBaudRate(QSerialPort::Baud38400);    //设置波特率
    myCom->setDataBits(QSerialPort::Data8);        //设置数据位
    myCom->setParity(QSerialPort::NoParity);   //设置校验
    myCom->setStopBits(QSerialPort::OneStop);   //设置停止位
    myCom->setFlowControl(QSerialPort::NoFlowControl);

    if(myCom->open(QIODevice::ReadWrite)){
        logTxt.setFileName(QCoreApplication::applicationDirPath()+"/log.txt");
        logTxt.open(QIODevice::Append | QIODevice::Text);

        timer->start(configIni.value("com/delytime").toInt());
        ComStatic->setText(tr("已成功打开串口：") + portName);
        ui->action_openCom->setEnabled(false);
        ui->action_closeCom->setEnabled(true);
        //qDebug() << myCom->handle();

        return true;
    }else{
        QMessageBox::critical(this, tr("打开失败"), tr("未能打开串口 ") + portName + tr("\n该串口设备不存在或已被占用"), QMessageBox::Ok);
        return false;
    }
}

void MainWindow::closeCom()
{
    timer->stop();
    logTxt.close();
    myCom->close();
    myCom = NULL;
    ui->action_openCom->setEnabled(true);
    ui->action_closeCom->setEnabled(false);
    ComStatic->setText(tr("已成功关闭串口"));
}

void MainWindow::time_update()
{
    static bool send07ed = true;

    if(SendIndex>MSA9020_COUNT)
        SendIndex = 1;
    if(modCount[SendIndex-1] > 10) {  // 如果连续发送10次命令都未收到反馈，则确定与该卡槽通讯中断
        this->findChild<QLabel*>("CHA_value_"+QString::number(SendIndex))->setText("Discon");
        this->findChild<QLabel*>("CHB_value_"+QString::number(SendIndex))->setText("Discon");
        modCount[SendIndex-1] = 10;
    }

    if(send07ed) {
        send03to(SendIndex);
        send07ed = false;
        //qDebug() << "send03to";
    } else
        if(!send07ed) {
            send07to(SendIndex++);
            send07ed = true;
            //qDebug() << "send07to";
        }
    qDebug() << "No" + QString::number(SendIndex) +":"+ QString::number(modCount[SendIndex-1]);
}

inline static quint16 modbusCRC(const char *data, qint32 len)
{
    quint16 crc16, chl, usehl;

    crc16 = 0xffff;
    chl = 0xa001;

    while (len--)
    {
        crc16 ^= *data++;
        for(int i=0;i<=7;i++)
        {
            usehl = crc16;
            crc16 >>= 1;
            if((usehl & 0x01) == 0x01)
                crc16 ^= chl;
        }
    }
    return (crc16);
}

void MainWindow::on_pushButton_clicked()
{
    qDebug() << myCom->handle();
}

void MainWindow::writeLog(QString w_r, char *buf, int len)
{
    QString s;
    for(int i=0;i<len;i++)
        s += QString().sprintf("%02x ",buf[i]);
    s = w_r +"("+QString::number(len)+"): "+ s + "\n";
    logTxt.write(s.toUtf8());
}

//读取数据
void MainWindow::readMyCom()
{
    static QByteArray buf;
    quint8 tfuncCode = 0;
    int buflength;

    buf += myCom->readAll();
    buflength = buf.length();
    if(buflength < 5) {
        return;
    }
    else
        tfuncCode = buf[1];

    if(buflength > 13) {
        qDebug() << QString::number(buf.length()) <<":"<<buf.toHex();
        buf.clear();
        return;
    }

    writeLog("R", buf.data(), buflength);
    //qDebug() << QString::number(buf.length()) <<":"<<buf.toHex();
    switch (tfuncCode) {
    case 0x03:
        if(buflength < 13) {
            return;   // 如果不全就继续返回接收
        } else {
            for(int i=0;i<13;i++) rx03.data[i] = buf[i];
            disp03(rx03);
            tfuncCode = 0;
            qDebug() << QString::number(buflength) <<":"<<buf.toHex();
            buf.clear();
        }
        break;
    case 0x07:
        if(buflength < 5) {
            return;   // 如果不全就继续返回接收
        } else {
            for(int i=0;i<5;i++) rx07.data[i] = buf[i];
            disp07(rx07);
            tfuncCode = 0;
            qDebug() << QString::number(buflength) <<":"<<buf.toHex();
            buf.clear();
        }
        break;
/*    default:
        if()
        qDebug() << QString::number(buf.length()) <<"(Error):"<<buf.toHex();
        buf.clear();  // 如果数据错误就丢弃
        break;  */
    }
}


// 设置界面上的报警值
void MainWindow::disp03(union func03_rx rx03)
{
    qint16 i, t_value, t_decimals;
    quint16 crc;
    float v;

    QDateTime dateTime = QDateTime::currentDateTime();
    double  now = dateTime.toTime_t();//当前时间转化为秒
    crc = modbusCRC(rx03.data, 11);
    if(crc == rx03.msa.crc) {
        for(i=0; i<MSA9020_COUNT; i++)
            if(rx03.msa.slaveAddress == modaddr[i]) {  // 查找Modbus地址为哪个卡槽反馈的数据
                modCount[i] = 0; // 确认接收数据，清零
                ui->mPlot->xAxis->setRange(now-3600*1, now); //, Qt::AlignRight);//x轴范围，从当前时间起往后推24小时

                // A 通道值
                t_value = swap16(rx03.msa.CHA_value);
                if((t_value >= -9999) && (t_value <= 9999)) {
                    t_decimals = qPow(10, swap16(rx03.msa.CHA_decimals));  // 值计算： value / (10^decimals)，value、decimals要高低位互换
                    v = (t_value+0.00001) / t_decimals;
                    this->findChild<QLabel*>("CHA_value_"+QString::number(i+1))->setText(QString::number(v,'f',2)); // +0.00001可将值强制为float型

                    mGraphA[i]->addData(now, v);

                } else
                    this->findChild<QLabel*>("CHA_value_"+QString::number(i+1))->setText("non");

                // B 通道值
                t_value = swap16(rx03.msa.CHB_value);
                if((t_value >= -9999) && (t_value <= 9999)) {
                    t_decimals = qPow(10, swap16(rx03.msa.CHB_decimals));
                    v = (t_value+0.00001) / t_decimals;
                    this->findChild<QLabel*>("CHB_value_"+QString::number(i+1))->setText(QString::number(v,'f',2));
                    mGraphB[i]->addData(now, v);
                } else
                    this->findChild<QLabel*>("CHB_value_"+QString::number(i+1))->setText("non");

                ui->mPlot->replot();
            }
    }
}

// 设置界面报警状态
void MainWindow::setAlarmStatus(QString objName, bool status)
{
    if(status)
        this->findChild<QLabel*>(objName)->setStyleSheet("background-color: rgb(255, 0, 0);color: rgb(255, 255, 255);");
    else
        this->findChild<QLabel*>(objName)->setStyleSheet("background-color: rgb(85, 0, 0);color: rgb(255, 255, 255);");
}

void MainWindow::disp07(union func07_rx rx07)
{
    int i;
    quint16 crc, t_value;
    crc = modbusCRC(rx07.data, 3);
    if(crc == rx07.msa.crc) {
        for(i=0; i<MSA9020_COUNT; i++)
            if(rx07.msa.slaveAddress == modaddr[i]) {  // 查找Modbus地址为哪个卡槽反馈的数据
                modCount[i] = 0; // 确认接收数据，清零
                t_value = rx07.msa.returnValue;
                // A 通道值 Al_1
                setAlarmStatus("Ac_"+QString::number(i+1), t_value & 0x01);
                setAlarmStatus("Al_"+QString::number(i+1), t_value & 0x02);
                setAlarmStatus("Ah_"+QString::number(i+1), t_value & 0x04);
                setAlarmStatus("Af_"+QString::number(i+1), t_value & 0x08);
                if(t_value & 0x08)
                    this->findChild<QLabel*>("CHA_value_"+QString::number(i+1))->setText("Fail");
                // B 通道值
                setAlarmStatus("Bc_"+QString::number(i+1), t_value & 0x10);
                setAlarmStatus("Bl_"+QString::number(i+1), t_value & 0x20);
                setAlarmStatus("Bh_"+QString::number(i+1), t_value & 0x40);
                setAlarmStatus("Bf_"+QString::number(i+1), t_value & 0x80);
                if(t_value & 0x80)
                    this->findChild<QLabel*>("CHB_value_"+QString::number(i+1))->setText("Fail");
            }
    }
}

void MainWindow::send03to(int index) // 向index号卡槽发送03命令
{
    if((modaddr[index-1]>=0) && (modaddr[index-1]<=255)) {
        tx03.msa.slaveAddress = modaddr[index-1];
        tx03.msa.funcCode = 0x03;
        tx03.msa.address = swap16(V_REAL_CHA); // A通道浓度值首地址，AB通道各2字节，共4字节
        tx03.msa.number = swap16(4);  // 获取4字节
        tx03.msa.crc = modbusCRC(tx03.data, 6);
        myCom->write(tx03.data, 8);
        writeLog("W", tx03.data, 8);
        modCount[index-1]++; // 发送计数加1,接收到就清0
    }
}

void MainWindow::send07to(int index) // 向index号卡槽发送07命令
{
    if((modaddr[index-1]>=0) && (modaddr[index-1]<=255)) {
        tx07.msa.slaveAddress = modaddr[index-1];
        tx07.msa.funcCode = 0x07;
        tx07.msa.crc = modbusCRC(tx07.data, 2);
        myCom->write(tx07.data, 4);
        writeLog("W", tx07.data, 4);
        modCount[index-1]++; // 发送计数加1,接收到就清0
    }
}

void MainWindow::on_action_openCom_triggered()
{
    openCom();
}

void MainWindow::on_action_closeCom_triggered()
{
    closeCom();
}

void MainWindow::on_action_options_triggered()
{
    options *opt = new options;
    opt->setWindowModality(Qt::ApplicationModal); //顶端窗口
    connect(opt, SIGNAL(sendData(bool)), this, SLOT(receiveData(bool)));
    opt->show();
}

void MainWindow::receiveData()
{
    ui->label_msg->setText("data");     //获取传递过来的数据
    initfrom();
}
